/**
 *  Project: Vorpal
 *  
 *  \file Objects.h
 *  \author Russ Webb
 *
 *  \brief Generic object class for objects created in vorpal, also includes Number, Boolean and Code classes within this file.
 *   
 */

#ifndef VORPAL_OBJECTS_H
#define VORPAL_OBJECTS_H

#include "Common.h"
#include "Messages.h"
#include "Instructions.h"
#include "Slots.h"
#include <list>
#include <vector>
#include <map>
#include <string>

namespace Vorpal{

	class VM;
	class NumberAllocator;
	
/** 
 * \class Object
 * 
 * \brief Generic class that all objects inherit, defines the base abilities of objects in Vorpal
 *
 */
class Object : public Slots{
public:
		Object() : refCount(0){ }
		
		virtual void Delete(){
			//cout << "\tRemoving: " << ToString() << endl;
			delete this;
		}
	
		virtual bool IsCall(Message msg, Handle<Code> &code, VM *vm);
	
		virtual bool Receive(Message msg, uint32_t numArgs, VM *vm);
	
		/**
		 * \brief The method called when 'object.print()' is called in vorpal. Should represent the object
		 * */
		virtual string ToString() const{
			ostringstream result;
			result << "Object: " << this;
			return result.str();
		}
		
		virtual double ToNumber() const{
			Failure(ERR_OBJ_CANNOT_TONUMBER);
			return 0;
		}
		
		virtual Message Hash() const{
			return HashString(ToString().c_str());
		}

		virtual Object *Clone(){
			Object *result = new Object();
			CopySlots(*result);
			return result;
		}
	
		void IncCount(){
			//static int count = 0; cout << "inc = " << ++count << endl;
			refCount += 1;
		}
	
		void DecCount(){
			//static int count = 0; cout << "dec = " << ++count << endl;
			Assert(refCount > 0);
			refCount -= 1;
			if(refCount == 0){
				if(traceAllocation){
					cout << "del: " << this->ToString() << endl;
				}
				Delete();
			}
		}
		
	virtual void Serialize(std::ostream &output, VM *vm, SerialMemory *mem = 0);	
	static Object *Unserialize(std::istream &input, VM *vm, SerialMemory *mem = 0);
	
	virtual void Store(std::ostream &output){ } 
	
	static Object *Create(std::istream &input){ 
		return new Object();
	}

protected:
	virtual ~Object(){ }
	
private:
	Object(const Object &other);
	Object &operator =(const Object &rhs);
	Object* operator &();
	
	uint32_t refCount;
	
#if trackInfluence
public:
	void PrintInfluence(){
		cout << ToString() << endl;
		loop(i, 0, influence.size()){
			cout << "\t" << influence[i] << endl;
		}
	}
	
	void AddInfluence(const Object &other){
		influence.push_back("\t: " + other.ToString());
		loop(i, 0, other.influence.size()){
			influence.push_back("\t" + other.influence[i]);
		}
	}
	
	void Clear(){
		influence.clear();
		Slots::Clear();
	}

private:
	vector< string > influence;
#endif
};
	
	template< class Type >
	Handle<Type> Translate(Object *prev){
		if(typeid(Type) != typeid(Object) && typeid(*prev) != typeid(Type)){
			Failure(ERR_INVALID_DATATYPE);
		}
		return Handle<Type>((Type *)prev);
	}

	template< class Type >
	Handle<Type> Translate(Handle<Object> prev){
		if(typeid(Type) != typeid(Object) && typeid(*prev.Ptr()) != typeid(Type)){
			Failure(ERR_INVALID_DATATYPE);
		}
		return Handle<Type>((Type *)prev.Ptr());
	}
	
	class Unique : public Object{
	public:
		Unique(const string &name, bool writable) : name(name), writable(writable){ }
		
		virtual void Set(Message key, Handle<Object> value){
			if(!writable){
				Failure(ERR_SETTING_SLOT_OF_TRUE_FALSE_NONE);
			}
			Object::Set(key, value);
		}
		
		virtual string ToString() const{
			return name;
		}
		
		virtual Message Hash() const{
			return HashString(name.c_str());
		}
		
		virtual Object *Clone(){
			return this;
		}
		
	private:
		Unique(const Unique &other);
		Unique &operator =(const Unique &rhs);
		Unique* operator &();
		
		string name;
		bool writable;
	};
	
	
/**  
 * \class Number
 * \brief Allows creation and use of Number objects in Vorpal.
 *
 */
    
	class Number : public Object{
	public:
		Number(double value = 0, NumberAllocator *owner = 0) : Object(), value(value), owner(owner){ }

		//static Number *New(double value);
		virtual void Delete();
			
		virtual bool Receive(Message msg, uint32_t numArgs, VM *vm);
		
		double Value() const{ return value; }
		void Value(double newValue){ value = newValue; }
				
		virtual string ToString() const{
			return ::ToString(value);
		}

		virtual double ToNumber() const{
			return value;
		}
		
		virtual Object *Clone(){
			Number *result = new Number(value);
			CopySlots(*result);
			return result;
		}

		virtual Message Hash() const{
			if(value == double(uint32_t(value))){
				return uint32_t(value);
			}
			else{
				return HashString(ToString().c_str());
			}
		}

		virtual void Store(std::ostream &output){ 
			output.write((const char *)&value, sizeof(value));
		} 
		
		static Object *Create(std::istream &input){ 
			double value;
			input.read((char *)&value, sizeof(value));
			return new Number(value);
		}
		
	private:
		double value;
		NumberAllocator *owner;

		Number(const Number &other);
		Number &operator =(const Number &rhs);
	};

	class Process : public Object{
	public:
		Process(const char *processId) : Object(), processId(processId){ }
		
		virtual bool Receive(Message msg, uint32_t numArgs, VM *vm);

		const string &Id() const{ return processId; }
		
		virtual void Print() const{
			cout << processId;
		}
		
		virtual string ToString() const{
			return ::ToString(processId);
		}
		
		virtual Object *Clone(){
			Process *result = new Process(processId.c_str());
			CopySlots(*result);
			return result;
		}
		
		virtual void Store(std::ostream &output){ 
			uint size = processId.length() + 1;
			output.write((const char *)&size, sizeof(size));
			output.write((const char *)processId.c_str(), size);
		} 
		
		static Object *Create(std::istream &input){ 
			uint size = 0;
			input.read((char *)&size, sizeof(size));
			char buffer[size];
			input.read((char *)buffer, size);
			return new Process(buffer);
		}
		
	private:
		string processId;
		
		Process(const Process &other);
		Process &operator =(const Process &rhs);
	};
	
	class Code : public Object{
	public:
		Code(const Cell *codeStart, bool copy = false) : Object(), deleteCode(copy), code(codeStart){ 
			if(copy){
				uint size = ExecutionInfo::CodeSizeInCells(codeStart);
				code = new Cell[size];
				memcpy((void *)code, codeStart, sizeof(Cell) * ExecutionInfo::CodeSizeInCells(codeStart));
			}
		}
		
		const Cell *CodePtr() const{ return code; }
		
		virtual void Print() const{
			printf("Code");
		}
		
		virtual string ToString() const{
			return ::ToString(code);
		}

		virtual Object *Clone(){
			Code *result = new Code(code, deleteCode);
			CopySlots(*result);
			return result;
		}

		virtual void Store(std::ostream &output){ 
			uint size = ExecutionInfo::CodeSizeInCells(code);
			output.write((const char *)&size, sizeof(size));
			output.write((const char *)code, sizeof(Cell) * size);
		} 
		
		static Object *Create(std::istream &input){ 
			uint size = 0;
			input.read((char *)&size, sizeof(size));
			Cell buffer[size];
			input.read((char *)buffer, sizeof(Cell) * size);
			return new Code(buffer, true);
		}
	protected:
		virtual ~Code(){  
			if(deleteCode){
				delete[] code;
			}
		} 

	private:
		bool deleteCode;
		const Cell *code;
	
		Code(const Code &other);
		Code &operator =(const Code &rhs);
	};
	
	/**
	 *	\class String
    	 *	\brief Adds support for String objects in Vorpal. Written by Oliver Drake.
   	 *	Messages Supported:\n
	 * 	  Symbolic:\n
	 * 		+  - concatenate two strings \n
	 * 		== - returns true if both strings are equal, false otherwise as Boolean. \n
	 *	  Normal: \n
         *		print() - print the string to stdout (with newline \\n). \n
	 *		length() - return the length of the string as a Number. \n
	 * 		strcmp(String other) - see C's strcmp documentation (Linux: man strcmp), returns Number. \n
	 *		substring([Number start_index[,Number end_index]]) - returns the substring from start_index as a String. \n
	 *  		find(String substring[,Number start_index[,Number end_index]]) - returns starting index of substring as String or false if substring not found as Boolean.
	 *
	 *	\author Oliver Drake (obd11)
	 */
class String : public Object{
public:
		String(const char *str) : str(str){ }
		String(const string &str) : str(str){ }
	
		static String *New(const string &str){
			String *result = new String(str);
			return result;
		}
	
		virtual bool Receive(Message msg, uint32_t numArgs, VM *vm);
		
		virtual string ToString() const{
			return str;
		}
		
		virtual uint32_t Length() const{
			return str.length();
		}

		virtual Object *Clone(){
			String *result = new String(str);
			CopySlots(*result);
			return result;
		}

		virtual Message Hash() const{
			return HashString(str.c_str());
		}
		
		virtual void Store(std::ostream &output){ 
			uint size = str.length() + 1;
			output.write((const char *)&size, sizeof(size));
			output.write((const char *)str.c_str(), size);
		} 
		
		static Object *Create(std::istream &input){ 
			uint size = 0;
			input.read((char *)&size, sizeof(size));
			char buffer[size];
			input.read((char *)buffer, size);
			return new String(buffer);
		}
	
	protected:
		virtual ~String(){ }
				
	private:
		string str;

		virtual int StringCompare(string str) const{
			return strcmp(this->str.c_str(), str.c_str()); // TODO: Using C's strcmp function
		}
		
		// TODO: allocating objects when searching for substrings is fairly confusing and error-prone
		// TODO: not sure why we want a no-parameter substring operation that returns the WHOLE string
	
		virtual String *Substring() const{
			return new String(str);
		}

		virtual String *Substring(uint32_t start) const{
			return new String(str.substr(start));
		}

		virtual String *Substring(uint32_t start, uint32_t end) const{
			return new String(str.substr(start, end));
		}

		virtual Handle<Object> Find(string substring, VM *vm) const{
			return Find(substring, 0, vm);
		}

		virtual Handle<Object> Find(string substring, uint32_t start, VM *vm) const;

		virtual Handle<Object> Find(string substring, uint32_t start, uint32_t end, VM *vm) const;
	
		string InputString(){
			// prime for input
			if (str.length() > 0){ cout << str; }
			else { cout << ">"; }
			cout << " ";
			// read & return max 256 characters
			char line[256];
			std::cin.getline(line, 256);
			return string(line);
		}
	};
	
	/**
	 * \brief A file object which can be used in Vorpal to perform basic read and write operations on files.
	 * A file can be created by sending a msg_open to a string.
	 * Read and write modes are seperate to reduce the likelihood of errors occuring.
	 * Failure(ERR_TYPE) is used when an error does occur.
	 */
	class FileIO : public Object{
	public:
		/*
		 * A fileIO object can be created for either reading or writing a file
		 * path specifies a path to the file (including its name)
		 * mode can be read or write
		 * write mode will delete whatever was previously in the file
		 */
		FileIO(const string &path, const string &mode): openMode('c') { OpenFile( path, mode ); }
		
		virtual ~FileIO(){ Close(); }
		
		/**
		 * Handle a message sent to a FIleIO object.
		 * If no message handler is found, Object::Recieve will be tried,
		 * then the object's slots will be checked for a suitable method
		 */
		virtual bool Receive(Message msg, uint32_t numArgs, VM *vm);
		
		/**
		 * \brief Reads the next line of the file
		 * returns "EOF" when it gets to the end of the file
		 */
		virtual string Read();
		
		/**
		 * \brief Writes the source string to the next line of the file
		 */
		virtual string Write(string source);
		
		/**
		 * \brief Close the file
		 * Returns false if file was already closed
		 */
		virtual bool Close();
		
		/**
		 * This is used when a file object recieves msg_print
		 */
		virtual string ToString() const {
			return ("File: " + filePath + ", mode: " + openMode);
		}
		virtual string Builtins_Str () const;
		
	private:
		/*
		 * \brief open a file for both input and output operations
		 */
		void OpenFile(string path, string mode);
		
		// members
		string filePath;
		std::fstream file;
		char openMode; ///should be c, r or w (closed, read or write)
	};
	
}

#endif



